package com.dmc.scrabblator;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Pattern;

import com.dmc.scrabblator.structures.CharVal;

public class WordRule
{

	//???d[..x]|?
	//[???d..]|?x??
	private List<Character> positions;

	private List<Character> modifiers;

	private Pattern posPat;

	//private Pattern modPat ;

	public static char NORM = '1';

	public static char DOUB_LET = '2';

	public static char TRIP_LET = '3';

	public static char DOUB_WORD = '4';

	public static char TRIP_WORD = '6';

	public WordRule()
	{
		positions = new ArrayList<Character>();
		posPat = Pattern.compile(".*");
		modifiers = new ArrayList<Character>();
	}

	public void setRule(List<Character> positions)
	{
		this.positions = new ArrayList<Character>();
		modifiers = new ArrayList<Character>();
		StringBuilder b = new StringBuilder("");
		final char top = '9';
		for (char c : positions)
		{
			if (c == ' ')
				continue;
			//This is a letter/word modifier
			if (c < top)
			{
				this.positions.add('?');
				modifiers.add(c);
				b.append(regexModifier('?'));
			}
			//TODO:Have to make the ??d..x?? logic.
			else
			{
				this.positions.add(c);
				modifiers.add(NORM);
				b.append(regexModifier(c));
			}
		}

		posPat = Pattern.compile(b.toString());
	}

	private String regexModifier(char c)
	{
		String ret = "" + c;//Bad
		if (c == '?')
			ret = ".?";
		return ret;
	}

	public void setModifiers(List<Character> modifiers)
	{
		this.modifiers = modifiers;
	}

	/**
	 * Returns null if it does not match.  Return the (modified value?) WordValue if it works
	 * @param potential
	 * @return
	 */
	public WordValue applyRule(WordValue potential)
	{//http://www.regexplanet.com/simple/index.html
		if (posPat == null)
		{
			return potential;
		}
		posPat.matcher(potential.getString()).matches();

		if (posPat.matcher(potential.getString()).matches())
		{
			//[x]TODO:Update the values using the modifiers list, synced with the positions list. fun!
			updateValue(potential);
			return potential;
		}

		return null;
	}

	public Set<WordValue> applyRule(Set<WordValue> words)
	{
		Set<WordValue> matches = new TreeSet<WordValue>();

		for (WordValue w : words)
		{
			WordValue word = applyRule(w);
			if (word != null)
			{
				addIfHighest(matches, word);
				//matches.add(word);
			}
			//Else it did not match so leave it out!
		}

		return matches;
	}

	private void addIfHighest(Set<WordValue> matches, WordValue word)
	{
		for (WordValue w : matches)
		{
			if (w.getString().equals(word.getString()))
			{
				if (w.getValue() < word.getValue())
				{
					matches.remove(w);
					matches.add(word);
					break;
				}
				else
					break;
			}
		}
		matches.add(word);
	}

	/**
	 * Modifies the object
	 * @param wordVal
	 */
	private void updateValue(WordValue wordVal)
	{
		//Find first matched char 
		int[] firstMatchs = getFirstMatchIndex(wordVal.getString());
		int pIndex = firstMatchs[0];
		int wIndex = firstMatchs[1];

		//mOffset is the offset for the modifier list (p>=w, we hope - if not, your getFirst is broken)
		int mOffset = pIndex - wIndex;

		List<CharVal> wordChars = wordVal.getWord();
		int newVal = 0;
		CharVal cVal;
		char c;
		char mod;
		int wordMult = 1;
		//Check each multiplier and either modify the char's value or the word's value
		for (int i = 0; i < wordChars.size(); i++)
		{
			cVal = wordChars.get(i);
			c = cVal.getChar();
			//Maybe modifiers weren't set at all.
			if (modifiers != null && modifiers.size() > 0)
			{
				mod = '?';
				if ((i + mOffset) < modifiers.size())//in case of run off
					mod = modifiers.get(i + mOffset);

				newVal += updateCharVal(cVal, mod);
				wordMult = updateMult(wordMult, mod);
			}
		}
		newVal *= wordMult;
		wordVal.setValue(newVal);

		//work back and work forward
	}

	private int updateCharVal(CharVal c, char mod)
	{
		//If it is a blank, it has no value
		if (c.isBlank())
			return 0;
		int val = WordValueUtil.getCharVal(c.getChar());

		if (mod == DOUB_LET)
			val *= 2;
		if (mod == TRIP_LET)
			val *= 3;
		return val;
	}

	private int updateMult(int wordMult, char mod)
	{
		int val = wordMult;
		if (mod == DOUB_WORD)
			val = Math.max(val, 2);
		if (mod == TRIP_WORD)
			val = Math.max(val, 3);
		return val;
	}

	/**
	 * Find where the first match between the Word and the Positions list meet up.
	 * Used to sync the Word and Modifiers
	 * @param word
	 * @return [PosIndex, WordIndex]
	 */
	private int[] getFirstMatchIndex(String word)
	{
		int wIndex = 0;
		int pIndex = 0;
		char[] wordChars = word.toCharArray();
		boolean exitFlag = false;
		for (int i = 0; i < positions.size(); i++)
		{
			char p = positions.get(i);
			if (p == '?')
				continue;
			//Found a character to match
			for (int j = 0; j < wordChars.length; j++)
			{
				char w = wordChars[j];
				if (p != w)
					continue;
				exitFlag = true;
				//Here are the potential matches
				pIndex = i;
				wIndex = j;
				//have the same chars - see if they match up the whole way down
				for (int ii = 1; //i + ii < positions.size()&&
				j + ii < wordChars.length; ii++)
				{
					if (i + ii == positions.size()) // We fell off the end.  Keep trying
					{
						exitFlag = false;
						break;
					}
					char pp = positions.get(i + ii);
					w = wordChars[j + ii];

					if (pp == '?')
						continue;
					if (pp != w)
					{
						exitFlag = false;
						break;
					}
				}
				if (exitFlag)
					break; //Need this for duplicate letters in equal length pattern/word combos

			}
			if (exitFlag)
				break;

		}
		int ar[] =
		{ pIndex, wIndex };
		return ar;
	}
	
	/**
	 * @return Returns the chars that exist in the rule to be added to the input string
	 */
	public String extractCharsFromRule()
	{
		String letters = "";
		for(char c:positions)
		{
			if(c>='a' && c<'Z')
				letters = letters+c;
		}
		return letters;
	}
}
